Skip to content

Modernize for PHP 8.x: attributes support, parser/CI/tooling updates, README fixes#69

Merged
voku merged 18 commits intomasterfrom
copilot/add-missing-php-8-features
Apr 9, 2026
Merged

Modernize for PHP 8.x: attributes support, parser/CI/tooling updates, README fixes#69
voku merged 18 commits intomasterfrom
copilot/add-missing-php-8-features

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 7, 2026

  • Remove the "Upload coverage results to Scrutinizer" step from .github/workflows/ci.yml
  • Remove .scrutinizer.yml

Copilot AI and others added 7 commits April 7, 2026 14:55
…ction/DNF types, never/true/false/null types

Agent-Logs-Url: https://github.com/voku/Simple-PHP-Code-Parser/sessions/42b017a8-8b52-424e-b8c3-4d1f4de1fc44

Co-authored-by: voku <264695+voku@users.noreply.github.com>
… version-specific syntax files

Agent-Logs-Url: https://github.com/voku/Simple-PHP-Code-Parser/sessions/aae312e9-242b-4745-8a50-1c9a59a2cba3

Co-authored-by: voku <264695+voku@users.noreply.github.com>
Copilot finished work on behalf of voku April 7, 2026 22:25
@voku voku marked this pull request as ready for review April 7, 2026 22:43
@voku
Copy link
Copy Markdown
Owner

voku commented Apr 7, 2026

/gemini review

@voku voku requested a review from Copilot April 7, 2026 22:48
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the library to support modern PHP features (8.0 through 8.3), including attributes, enums, intersection types, DNF types, and typed class constants. It introduces a new PHPAttribute model and a PHPEnum model, while enhancing existing models to capture attribute data and specific PHP 8.3 flags like #[!Override]. Key architectural changes include a two-pass AST traversal to ensure name resolution before model extraction and logic to prevent fatal errors when parsing PHP 8.2+ syntax on older PHP versions. Feedback highlights a critical compatibility issue with nikic/php-parser v4, where a v5-only method was used, and identifies logic errors in type string formatting where backslashes are incorrectly prepended to complex types (Union/Intersection/Nullable). Additionally, a redundant check for the Override attribute name was noted.

]
)
);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The method createForNewestSupportedVersion() was introduced in nikic/php-parser v5.0. However, the composer.json in this project specifies "nikic/php-parser": "~4.18". Using this method with v4.x will result in a fatal error. You should use the v4 compatible create() method with ParserFactory::PREFER_PHP7 to support modern PHP syntax.

        $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);

Comment on lines +517 to +564
public static function typeNodeToString($typeNode): ?string
{
if ($typeNode === null) {
return null;
}

if ($typeNode instanceof \PhpParser\Node\NullableType) {
$inner = self::typeNodeToString($typeNode->type);

return $inner !== null ? 'null|' . $inner : 'null';
}

if ($typeNode instanceof \PhpParser\Node\UnionType) {
$parts = [];
foreach ($typeNode->types as $inner) {
if ($inner instanceof \PhpParser\Node\IntersectionType) {
$subParts = [];
foreach ($inner->types as $subType) {
$subParts[] = self::typeNodeToString($subType) ?? 'mixed';
}
$parts[] = '(' . \implode('&', $subParts) . ')';
} else {
$parts[] = self::typeNodeToString($inner) ?? 'mixed';
}
}

return \implode('|', $parts);
}

if ($typeNode instanceof \PhpParser\Node\IntersectionType) {
$parts = [];
foreach ($typeNode->types as $inner) {
$parts[] = self::typeNodeToString($inner) ?? 'mixed';
}

return \implode('&', $parts);
}

if (\method_exists($typeNode, 'toString')) {
return $typeNode->toString();
}

if (\property_exists($typeNode, 'name') && $typeNode->name) {
return $typeNode->name;
}

return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of typeNodeToString returns the string representation of class names without a leading backslash. This causes issues in PHPProperty and PHPParameter where a backslash is naively prepended to the entire type string, resulting in invalid types like \null|MyClass or \A|B for union types.

It is better to handle the leading backslash for class names (represented as Name nodes) directly within this helper to ensure consistency across all type formats (nullable, union, intersection, DNF).

    public static function typeNodeToString($typeNode): ?string
    {
        if ($typeNode === null) {
            return null;
        }

        if ($typeNode instanceof \PhpParser\Node\NullableType) {
            $inner = self::typeNodeToString($typeNode->type);

            return $inner !== null ? 'null|' . $inner : 'null';
        }

        if ($typeNode instanceof \PhpParser\Node\UnionType) {
            $parts = [];
            foreach ($typeNode->types as $inner) {
                if ($inner instanceof \PhpParser\Node\IntersectionType) {
                    $subParts = [];
                    foreach ($inner->types as $subType) {
                        $subParts[] = self::typeNodeToString($subType) ?? 'mixed';
                    }
                    $parts[] = '(' . \implode('&', $subParts) . ')';
                } else {
                    $parts[] = self::typeNodeToString($inner) ?? 'mixed';
                }
            }

            return \implode('|', $parts);
        }

        if ($typeNode instanceof \PhpParser\Node\IntersectionType) {
            $parts = [];
            foreach ($typeNode->types as $inner) {
                $parts[] = self::typeNodeToString($inner) ?? 'mixed';
            }

            return \implode('&', $parts);
        }

        if ($typeNode instanceof \PhpParser\Node\Name) {
            return '\\' . $typeNode->toString();
        }

        if (\method_exists($typeNode, 'toString')) {
            return $typeNode->toString();
        }

        if (\property_exists($typeNode, 'name') && $typeNode->name) {
            return (string) $typeNode->name;
        }

        return null;
    }

Comment on lines +89 to 97
$typeStr = Utils::typeNodeToString($node->type);
if ($typeStr !== null) {
if (\method_exists($node->type, 'getParts')) {
$this->type = '\\' . $typeStr;
} else {
$this->type = $typeStr;
}
} elseif (\property_exists($node->type, 'name') && $node->type->name) {
$this->type = $node->type->name;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Prepending a backslash to the result of typeNodeToString is incorrect for complex types. For example, a nullable type ?MyClass becomes null|MyClass, and prepending a backslash results in \null|MyClass. Similarly, union types like A|B become \A|B.

If Utils::typeNodeToString is updated to handle leading backslashes for Name nodes, this logic can be simplified and made correct for all PHP 8.x type variations.

                $typeStr = Utils::typeNodeToString($node->type);
                if ($typeStr !== null) {
                    $this->type = $typeStr;
                }

Comment on lines +84 to 92
$typeStr = Utils::typeNodeToString($parameter->type);
if ($typeStr !== null) {
if (\method_exists($parameter->type, 'getParts')) {
$this->type = '\\' . $typeStr;
} else {
$this->type = $typeStr;
}
} elseif (\property_exists($parameter->type, 'name')) {
$this->type = $parameter->type->name;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to PHPProperty, prepending a backslash here leads to invalid type strings for complex types (Union, Intersection, Nullable). The formatting should be handled within Utils::typeNodeToString to ensure each class part of a complex type is correctly prefixed.

                $typeStr = Utils::typeNodeToString($parameter->type);
                if ($typeStr !== null) {
                    $this->type = $typeStr;
                }

// Detect #[\Override] (PHP 8.3+)
if ($this->is_override === null) {
foreach ($this->attributes as $attr) {
if ($attr->name === 'Override' || $attr->name === '\\Override') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check for \\Override is unnecessary. When using NameResolver, the attribute name is resolved to its fully qualified form without a leading backslash in its string representation (e.g., just Override for the built-in attribute). The \\Override string will never match the output of toString() on a FullyQualified name node in php-parser v4.

                if ($attr->name === 'Override') {

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the Simple PHP Code Parser for PHP 8.x syntax by expanding the parsed model (attributes, enums, newer type features), updating parsing/traversal strategy for correct name resolution, and aligning CI/tooling/docs with the new minimum PHP version.

Changes:

  • Add support for PHP 8+ attributes across classes/traits/interfaces/functions/methods/properties/parameters/constants.
  • Add enum parsing support (including cases, backing type, methods, constants) and extend container/parser wiring accordingly.
  • Update tooling/docs/CI: bump minimum PHP to 8.1, update php-parser + PHPUnit constraints, refresh workflow actions, and adjust README.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/voku/SimplePhpParser/Parsers/PhpCodeParser.php Switch to newest-supported parser + split traversal into 2 passes (resolve names before ASTVisitor).
src/voku/SimplePhpParser/Parsers/Visitors/ASTVisitor.php Add Enum_ handling + attach constants to enums/traits.
src/voku/SimplePhpParser/Parsers/Helper/ParserContainer.php Add enum storage + getters/setters.
src/voku/SimplePhpParser/Parsers/Helper/Utils.php Add attribute extraction helpers + robust type-node-to-string conversion + normalize never.
src/voku/SimplePhpParser/Model/* Add PHPAttribute + PHPEnum models; add attributes support to existing models; improve type parsing; add typed-const support.
tests/ParserTest.php Expand test coverage for enums, attributes, intersection/DNF/standalone types, typed consts, Override detection, modern syntax; adjust existing expectations.
tests/Dummy*.php Add fixtures for attributes/enums/new type syntax/trait constants/typed constants/Override and modern syntax.
README.md Document new entities (enums/attributes), requirements, supported features, and update links.
composer.json Require PHP >= 8.1; bump php-parser and PHPUnit dev constraint.
.github/workflows/ci.yml Update PHP matrix (8.1–8.4) and modernize actions/cache/output syntax; adjust phpstan step.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +58 to +61
// PHP < 8.2 raises an uncatchable E_COMPILE_ERROR for certain PHP 8.2+ syntax
// (standalone true/false/null types, DNF types, readonly class). Skip autoloading
// in that case; AST data is still read from the node below.
$canAutoload = \PHP_VERSION_ID >= 80200 || !self::nodeUsesPHP82PlusSyntax($node);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$canAutoload only guards against PHP 8.2+ syntax on PHP < 8.2, but it doesn’t account for PHP 8.3+ syntax that is also fatal on earlier runtimes (e.g., typed class constants). On PHP 8.2, parsing a PHP 8.3 class that contains typed constants can still trigger an uncatchable compile error when class_exists(..., true) autoloads it. Consider extending the detection logic (or generalizing it) to also detect 8.3+ constructs (e.g., Stmt\ClassConst with non-null type) and gate autoloading on PHP_VERSION_ID >= 80300 for that case.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml
Comment on lines 28 to 33
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@2.25.5
uses: shivammathur/setup-php@v2
with:
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow switched from pinning actions/checkout/setup-php to a specific commit SHA to using floating tags (@v4, @v2). This weakens supply-chain security because the referenced code can change without review. Prefer pinning third-party actions to an immutable commit SHA (and optionally keeping a comment with the corresponding version).

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml
Comment on lines 67 to 72
- name: Run phpstan
continue-on-error: true
if: ${{ matrix.php == '7.4' }}
if: ${{ matrix.php == '8.3' }}
run: |
composer require phpstan/phpstan --dev --no-interaction
php vendor/bin/phpstan analyse
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phpstan step installs dependencies via composer require during CI, which mutates composer.json/composer.lock and makes the run non-reproducible (and can interact poorly with caching). It’s more deterministic to declare phpstan in require-dev (or install it with composer require --no-update plus a composer update that respects the lock), then run vendor/bin/phpstan.

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated
Comment on lines +141 to +142
echo $attr->name; // e.g. "MyAttribute"
echo $attr->arguments; // constructor arguments
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README example uses echo $attr->arguments;, but $arguments is an array, so this will print Array and can emit a notice; it doesn’t demonstrate how to inspect arguments. Consider using print_r($attr->arguments, true), var_export(...), or iterating the array to show key/value pairs.

Suggested change
echo $attr->name; // e.g. "MyAttribute"
echo $attr->arguments; // constructor arguments
echo $attr->name; // e.g. "MyAttribute"
print_r($attr->arguments); // constructor arguments

Copilot uses AI. Check for mistakes.
Comment thread tests/DummyModernSyntax.php Outdated
namespace voku\tests;

/**
* Test fixture for PHP 8.1 fibers and first-class callable syntax.
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file-level docblock mentions “PHP 8.1 fibers”, but this fixture doesn’t include any Fiber usage (it covers first-class callable, match, named arguments, nullsafe). Consider updating the comment to match what’s actually exercised so it stays accurate over time.

Suggested change
* Test fixture for PHP 8.1 fibers and first-class callable syntax.
* Test fixture for modern PHP syntax: first-class callables, match,
* named arguments, and the nullsafe operator.

Copilot uses AI. Check for mistakes.
@voku
Copy link
Copy Markdown
Owner

voku commented Apr 8, 2026

@copilot apply changes based on the comments in this thread

…equire-dev, README/docblock fixes

Agent-Logs-Url: https://github.com/voku/Simple-PHP-Code-Parser/sessions/0f1d61ca-2bd8-4dd3-9c09-972748b9ba0e

Co-authored-by: voku <264695+voku@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 8, 2026

@copilot apply changes based on the comments in this thread

Applied all changes from the review thread in commit 9bbb352:

  • PHPClass.php: Added nodeUsesPHP83PlusSyntax() that detects typed class constants (Stmt\ClassConst with non-null type), and extended the $canAutoload guard to also check PHP_VERSION_ID >= 80300 for PHP 8.3+ constructs.
  • ci.yml: Pinned all third-party actions to immutable commit SHAs (checkout@v4.2.2, setup-php@v2.37.0, cache@v4.3.0, codecov-action@v4.5.0, upload-artifact@v4.4.3).
  • ci.yml + composer.json: Moved phpstan/phpstan ^1.10 to require-dev; removed the inline composer require phpstan/phpstan from the CI step.
  • README.md: Changed echo $attr->arguments;print_r($attr->arguments); with a clarifying (array) note.
  • DummyModernSyntax.php: Updated the docblock to accurately describe the actual fixture content (first-class callables, match, named arguments, nullsafe operator).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/8430ca5c93b5b933ef1aaf8dd93f80d0c55d2349
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/async/zipball/b9641ac600b4b144e71a87dcf1be4d41dd3a3548
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/filesystem/zipball/385933fdf47ad2db195c859523a8d854792dbad2
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/promise-stream/zipball/5c7ec3450f558deb779742e33967d837e2db7871
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/voku/simple-cache/zipball/d6578b2b1c7a1ed36c4a862cae826b703520e73b
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)
  • https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/lqTMhO /usr/bin/composer install -q (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/bxtGFo /usr/bin/composer update -q tnet/tools/git che/composer/vcsiptables (http block)
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/PmuR7p /usr/bin/composer install --no-interaction ndor/bin/git --git-dir git nfig/composer/vendor/bin/git git tag (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of voku April 8, 2026 01:11
@voku
Copy link
Copy Markdown
Owner

voku commented Apr 8, 2026

@copilot Code Review
This pull request updates the library to support modern PHP features (8.0 through 8.3), including attributes, enums, intersection types, DNF types, and typed class constants. It introduces a new PHPAttribute model and a PHPEnum model, while enhancing existing models to capture attribute data and specific PHP 8.3 flags like #[!Override]. Key architectural changes include a two-pass AST traversal to ensure name resolution before model extraction and logic to prevent fatal errors when parsing PHP 8.2+ syntax on older PHP versions. Feedback highlights a critical compatibility issue with nikic/php-parser v4, where a v5-only method was used, and identifies logic errors in type string formatting where backslashes are incorrectly prepended to complex types (Union/Intersection/Nullable). Additionally, a redundant check for the Override attribute name was noted.

src/voku/SimplePhpParser/Parsers/PhpCodeParser.php
]
)
);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
@gemini-code-assist
gemini-code-assist bot
2 hours ago
critical

The method createForNewestSupportedVersion() was introduced in nikic/php-parser v5.0. However, the composer.json in this project specifies "nikic/php-parser": "~4.18". Using this method with v4.x will result in a fatal error. You should use the v4 compatible create() method with ParserFactory::PREFER_PHP7 to support modern PHP syntax.

    $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);

@voku Reply...
src/voku/SimplePhpParser/Parsers/Helper/Utils.php
Comment on lines +517 to +564
public static function typeNodeToString($typeNode): ?string
{
if ($typeNode === null) {
return null;
}

    if ($typeNode instanceof \PhpParser\Node\NullableType) {
        $inner = self::typeNodeToString($typeNode->type);

        return $inner !== null ? 'null|' . $inner : 'null';
    }

    if ($typeNode instanceof \PhpParser\Node\UnionType) {
        $parts = [];
        foreach ($typeNode->types as $inner) {
            if ($inner instanceof \PhpParser\Node\IntersectionType) {
                $subParts = [];
                foreach ($inner->types as $subType) {
                    $subParts[] = self::typeNodeToString($subType) ?? 'mixed';
                }
                $parts[] = '(' . \implode('&', $subParts) . ')';
            } else {
                $parts[] = self::typeNodeToString($inner) ?? 'mixed';
            }
        }

        return \implode('|', $parts);
    }

    if ($typeNode instanceof \PhpParser\Node\IntersectionType) {
        $parts = [];
        foreach ($typeNode->types as $inner) {
            $parts[] = self::typeNodeToString($inner) ?? 'mixed';
        }

        return \implode('&', $parts);
    }

    if (\method_exists($typeNode, 'toString')) {
        return $typeNode->toString();
    }

    if (\property_exists($typeNode, 'name') && $typeNode->name) {
        return $typeNode->name;
    }

    return null;
}

@gemini-code-assist
gemini-code-assist bot
2 hours ago
high

The current implementation of typeNodeToString returns the string representation of class names without a leading backslash. This causes issues in PHPProperty and PHPParameter where a backslash is naively prepended to the entire type string, resulting in invalid types like \null|MyClass or \A|B for union types.

It is better to handle the leading backslash for class names (represented as Name nodes) directly within this helper to ensure consistency across all type formats (nullable, union, intersection, DNF).

public static function typeNodeToString($typeNode): ?string
{
    if ($typeNode === null) {
        return null;
    }

    if ($typeNode instanceof \PhpParser\Node\NullableType) {
        $inner = self::typeNodeToString($typeNode->type);

        return $inner !== null ? 'null|' . $inner : 'null';
    }

    if ($typeNode instanceof \PhpParser\Node\UnionType) {
        $parts = [];
        foreach ($typeNode->types as $inner) {
            if ($inner instanceof \PhpParser\Node\IntersectionType) {
                $subParts = [];
                foreach ($inner->types as $subType) {
                    $subParts[] = self::typeNodeToString($subType) ?? 'mixed';
                }
                $parts[] = '(' . \implode('&', $subParts) . ')';
            } else {
                $parts[] = self::typeNodeToString($inner) ?? 'mixed';
            }
        }

        return \implode('|', $parts);
    }

    if ($typeNode instanceof \PhpParser\Node\IntersectionType) {
        $parts = [];
        foreach ($typeNode->types as $inner) {
            $parts[] = self::typeNodeToString($inner) ?? 'mixed';
        }

        return \implode('&', $parts);
    }

    if ($typeNode instanceof \PhpParser\Node\Name) {
        return '\\' . $typeNode->toString();
    }

    if (\method_exists($typeNode, 'toString')) {
        return $typeNode->toString();
    }

    if (\property_exists($typeNode, 'name') && $typeNode->name) {
        return (string) $typeNode->name;
    }

    return null;
}

@voku Reply...
src/voku/SimplePhpParser/Model/PHPProperty.php
Comment on lines +89 to 97
$typeStr = Utils::typeNodeToString($node->type);
if ($typeStr !== null) {
if (\method_exists($node->type, 'getParts')) {
$this->type = '\' . $typeStr;
} else {
$this->type = $typeStr;
}
} elseif (\property_exists($node->type, 'name') && $node->type->name) {
$this->type = $node->type->name;
}
}
@gemini-code-assist
gemini-code-assist bot
2 hours ago
high

Prepending a backslash to the result of typeNodeToString is incorrect for complex types. For example, a nullable type ?MyClass becomes null|MyClass, and prepending a backslash results in \null|MyClass. Similarly, union types like A|B become \A|B.

If Utils::typeNodeToString is updated to handle leading backslashes for Name nodes, this logic can be simplified and made correct for all PHP 8.x type variations.

            $typeStr = Utils::typeNodeToString($node->type);
            if ($typeStr !== null) {
                $this->type = $typeStr;
            }

@voku Reply...
src/voku/SimplePhpParser/Model/PHPParameter.php
Comment on lines +84 to 92
$typeStr = Utils::typeNodeToString($parameter->type);
if ($typeStr !== null) {
if (\method_exists($parameter->type, 'getParts')) {
$this->type = '\' . $typeStr;
} else {
$this->type = $typeStr;
}
} elseif (\property_exists($parameter->type, 'name')) {
$this->type = $parameter->type->name;
}
}
@gemini-code-assist
gemini-code-assist bot
2 hours ago
high

Similar to PHPProperty, prepending a backslash here leads to invalid type strings for complex types (Union, Intersection, Nullable). The formatting should be handled within Utils::typeNodeToString to ensure each class part of a complex type is correctly prefixed.

            $typeStr = Utils::typeNodeToString($parameter->type);
            if ($typeStr !== null) {
                $this->type = $typeStr;
            }

@voku Reply...
src/voku/SimplePhpParser/Model/PHPMethod.php
// Detect #[\Override] (PHP 8.3+)
if ($this->is_override === null) {
foreach ($this->attributes as $attr) {
if ($attr->name === 'Override' || $attr->name === '\Override') {
@gemini-code-assist
gemini-code-assist bot
2 hours ago
medium

The check for \Override is unnecessary. When using NameResolver, the attribute name is resolved to its fully qualified form without a leading backslash in its string representation (e.g., just Override for the built-in attribute). The \Override string will never match the output of toString() on a FullyQualified name node in php-parser v4.

            if ($attr->name === 'Override') {

@voku Reply...
Copilot AI reviewed 2 hours ago
Copilot AI
left a comment
Pull request overview
This PR modernizes the Simple PHP Code Parser for PHP 8.x syntax by expanding the parsed model (attributes, enums, newer type features), updating parsing/traversal strategy for correct name resolution, and aligning CI/tooling/docs with the new minimum PHP version.

Changes:

Add support for PHP 8+ attributes across classes/traits/interfaces/functions/methods/properties/parameters/constants.
Add enum parsing support (including cases, backing type, methods, constants) and extend container/parser wiring accordingly.
Update tooling/docs/CI: bump minimum PHP to 8.1, update php-parser + PHPUnit constraints, refresh workflow actions, and adjust README.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.

Show a summary per file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

src/voku/SimplePhpParser/Model/PHPClass.php
Outdated
Comment on lines +58 to +61
// PHP < 8.2 raises an uncatchable E_COMPILE_ERROR for certain PHP 8.2+ syntax
// (standalone true/false/null types, DNF types, readonly class). Skip autoloading
// in that case; AST data is still read from the node below.
$canAutoload = \PHP_VERSION_ID >= 80200 || !self::nodeUsesPHP82PlusSyntax($node);
Copilot AI
2 hours ago
$canAutoload only guards against PHP 8.2+ syntax on PHP < 8.2, but it doesn’t account for PHP 8.3+ syntax that is also fatal on earlier runtimes (e.g., typed class constants). On PHP 8.2, parsing a PHP 8.3 class that contains typed constants can still trigger an uncatchable compile error when class_exists(..., true) autoloads it. Consider extending the detection logic (or generalizing it) to also detect 8.3+ constructs (e.g., Stmt\ClassConst with non-null type) and gate autoloading on PHP_VERSION_ID >= 80300 for that case.

Copilot uses AI. Check for mistakes.
@voku Reply...
.github/workflows/ci.yml
Comment on lines 28 to 33
- name: Checkout code
uses: actions/checkout@3df4ab1 # v4
uses: actions/checkout@v4

  - name: Setup PHP
    uses: shivammathur/setup-php@2.25.5
    uses: shivammathur/setup-php@v2
    with:

Copilot AI
2 hours ago
The workflow switched from pinning actions/checkout/setup-php to a specific commit SHA to using floating tags (@v4, @v2). This weakens supply-chain security because the referenced code can change without review. Prefer pinning third-party actions to an immutable commit SHA (and optionally keeping a comment with the corresponding version).

Copilot uses AI. Check for mistakes.
@voku Reply...
.github/workflows/ci.yml
Comment on lines 67 to 72
- name: Run phpstan
continue-on-error: true
if: ${{ matrix.php == '7.4' }}
if: ${{ matrix.php == '8.3' }}
run: |
composer require phpstan/phpstan --dev --no-interaction
php vendor/bin/phpstan analyse
Copilot AI
2 hours ago
The phpstan step installs dependencies via composer require during CI, which mutates composer.json/composer.lock and makes the run non-reproducible (and can interact poorly with caching). It’s more deterministic to declare phpstan in require-dev (or install it with composer require --no-update plus a composer update that respects the lock), then run vendor/bin/phpstan.

Copilot uses AI. Check for mistakes.
@voku Reply...
README.md
Outdated
Comment on lines +141 to +142
echo $attr->name; // e.g. "MyAttribute"
echo $attr->arguments; // constructor arguments
Copilot AI
2 hours ago
The README example uses echo $attr->arguments;, but $arguments is an array, so this will print Array and can emit a notice; it doesn’t demonstrate how to inspect arguments. Consider using print_r($attr->arguments, true), var_export(...), or iterating the array to show key/value pairs.

Suggested change
echo $attr->name; // e.g. "MyAttribute"
echo $attr->arguments; // constructor arguments
echo $attr->name; // e.g. "MyAttribute"
print_r($attr->arguments); // constructor arguments
Copilot uses AI. Check for mistakes.
@voku Reply...
tests/DummyModernSyntax.php
Outdated
namespace voku\tests;

/**

  • Test fixture for PHP 8.1 fibers and first-class callable syntax.
    Copilot AI
    2 hours ago
    The file-level docblock mentions “PHP 8.1 fibers”, but this fixture doesn’t include any Fiber usage (it covers first-class callable, match, named arguments, nullsafe). Consider updating the comment to match what’s actually exercised so it stays accurate over time.

Suggested change

  • Test fixture for PHP 8.1 fibers and first-class callable syntax.
  • Test fixture for modern PHP syntax: first-class callables, match,
  • named arguments, and the nullsafe operator.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 8, 2026

@copilot Code Review
This pull request updates the library to support modern PHP features (8.0 through 8.3), including attributes, enums, intersection types, DNF types, and typed class constants. It ...

Applied all changes from the Gemini review in commit 1b22dd3:

  • PhpCodeParser.php: Replaced v5-only createForNewestSupportedVersion() with create(ParserFactory::PREFER_PHP7) — compatible with nikic/php-parser ~4.18 (v4.x).
  • Utils.php typeNodeToString(): Added an explicit Name node check before the generic toString() check: if ($typeNode instanceof \PhpParser\Node\Name) { return '\\' . $typeNode->toString(); } — ensures FQN class names always have the \ prefix in all compound type contexts (Union/Intersection/Nullable/DNF), eliminating the inconsistency.
  • PHPProperty.php / PHPParameter.php: Simplified type assignment to $this->type = $typeStr — the \ prefix is now handled once inside typeNodeToString(), not duplicated in callers.
  • PHPMethod.php: Removed the redundant || $attr->name === '\\Override' condition (in both AST and reflection paths) — NameResolver resolves Override to its unqualified toString() form.
  • ParserTest.php: Updated the getCallable()->returnType assertion to '\Closure' to reflect the corrected FQN output for the AST-only code path.

All 38 tests pass.

Copilot finished work on behalf of voku April 8, 2026 01:40
@voku
Copy link
Copy Markdown
Owner

voku commented Apr 8, 2026

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly updates the library to support PHP 8.0 through 8.3 features, including attributes, enums, intersection types, DNF types, and typed class constants. The minimum PHP requirement is bumped to 8.1, and the parsing logic is refactored into a two-pass traversal to ensure names are fully resolved before model extraction. Additionally, logic was added to prevent uncatchable compile errors when parsing newer syntax on older PHP versions. Feedback focuses on improving type safety when handling reflection types and optimizing class existence checks to account for already-loaded classes.

Comment on lines +162 to +166
if (\method_exists($backingType, 'getName')) {
$this->scalarType = $backingType->getName();
} else {
$this->scalarType = (string) $backingType;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While method_exists works, it is more idiomatic and type-safe to check if $backingType is an instance of ReflectionNamedType before calling getName(), as ReflectionEnum::getBackingType() is guaranteed to return either null or a ReflectionNamedType in PHP 8.1+.

            if ($backingType instanceof \ReflectionNamedType) {
                $this->scalarType = $backingType->getName();
            } else {
                $this->scalarType = (string) $backingType;
            }

Comment on lines +62 to +63
$canAutoload = (\PHP_VERSION_ID >= 80200 || !self::nodeUsesPHP82PlusSyntax($node))
&& (\PHP_VERSION_ID >= 80300 || !self::nodeUsesPHP83PlusSyntax($node));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic to skip autoloading for PHP 8.2+ or 8.3+ syntax on older PHP versions is correct to avoid uncatchable E_COMPILE_ERROR. However, if the class is already loaded in memory (e.g., via a manual require or a different autoloader), we should still attempt to use reflection. Consider checking class_exists($this->name, false) first.

        $classExists = \class_exists($this->name, false);
        if (!$classExists && $canAutoload) {
            try {
                if (\class_exists($this->name, true)) {
                    $classExists = true;
                }
            } catch (\Throwable $e) {
                // nothing
            }
        }

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
6.4% Duplication on New Code (required ≤ 3%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copilot finished work on behalf of voku April 9, 2026 15:12
Copilot finished work on behalf of voku April 9, 2026 21:37
@voku voku merged commit 8c24685 into master Apr 9, 2026
12 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants